package com.tanx.cqrs.infrastructure.spring.eventsourcing;

import com.tanx.cqrs.event.Event;
import com.tanx.cqrs.event.store.EventStoreRepository;
import com.tanx.cqrs.eventsourcing.EventSourcingMata;
import com.tanx.cqrs.eventsourcing.EventSourcingRepository;
import com.tanx.cqrs.eventsourcing.Id;
import com.tanx.cqrs.eventsourcing.handler.EventSourcingHandlerResolver;
import com.tanx.cqrs.eventsourcing.snapshot.Snapshot;
import com.tanx.cqrs.eventsourcing.snapshot.SnapshotBuildStrategy;
import com.tanx.cqrs.infrastructure.spring.event.store.EventStoreRepositoryFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 聚合溯源
 */
public class EventSourcingRepositoryImpl implements EventSourcingRepository {
    private EventSourcingHandlerResolver resolver;
    private ApplicationContext context;
    private EventStoreRepositoryFactory factory;
    private AutowireCapableBeanFactory capableBeanFactory;

    public EventSourcingRepositoryImpl(EventSourcingHandlerResolver resolver, ApplicationContext context,
                                       EventStoreRepositoryFactory factory, AutowireCapableBeanFactory beanFactory) {
        this.resolver = resolver;
        this.context = context;
        this.factory = factory;
        this.capableBeanFactory = beanFactory;
    }

    /**
     * 对每一个 聚合进行溯源
     *
     * @param aggregateClass 溯源的聚合类
     * @param id             溯源的聚合唯一标识
     * @param <T>            溯源的聚合类型
     * @return 聚合实例
     */
    @Override
    public <T, E extends Event> T eventSourcing(Class<T> aggregateClass, Object id) {
        //排除没有id的聚合
        if (StringUtils.isEmpty(id)) {
            return context.getBean(aggregateClass);
        }
        //1,找到事件溯源的存储库
        EventStoreRepository<T, E> repository = getRepository(aggregateClass);
        SnapshotBuildStrategy<T> snapshotBuildStrategy = repository.getSnapshotBuildStrategy();
        //2,先查询是否有快照
        Snapshot snapshot = snapshotBuildStrategy.getSnapshotRepository().findTop1SnapByCreateDateTimeDesc(id);
        //3,获取到溯源的事件
        List eventList;
        T aggregate;
        if (snapshot != null) {
            //3,如果存在快照,先以快照为基础,然后加载快照产生后发生的事件
            aggregate = snapshotBuildStrategy.getSnapshotRepository().getAggregate(snapshot);
            eventList = repository.findByCreateDateTimeAfterAndIdByCreateDateTimeAsc(snapshot.getCreateDateTime(), id);
        } else {
            aggregate = context.getBean(aggregateClass);
            setAggregateId(aggregate, id, aggregateClass);
            eventList = repository.findByIdByCreateDateTimeAsc(id);
        }
        capableBeanFactory.autowireBean(aggregate);
        //4,进行事件溯源
        resolver.eventSourcing(aggregate, eventList);
        //5,创建快照
        if (snapshotBuildStrategy.needCreate(aggregate, eventList)) {
            snapshotBuildStrategy.createSnap(aggregate, id);
        }
        return aggregate;
    }

    private <T> void setAggregateId(T aggregate, Object id, Class<T> aggregateClass) {
        Field[] declaredFields = aggregateClass.getDeclaredFields();
        try {
            for (Field declaredField : declaredFields) {
                Id annotation = declaredField.getAnnotation(Id.class);
                if (annotation != null) {
                    declaredField.setAccessible(true);
                    declaredField.set(aggregate, id);
                    return;
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("设置聚合唯一标识失败");
        }
    }

    /**
     * 优先级:
     * 1,注解上使用了名字
     * 2,注解上没有名字,则使用类型
     */
    @SuppressWarnings("unchecked")
    private <T, E extends Event> EventStoreRepository<T, E> getRepository(Class<T> aggregateClass) {
        return factory.getStoreRepository(aggregateClass);
    }

    @Override
    public List<Object> eventSourcingList(List<EventSourcingMata> matas) {
        return matas.parallelStream().map(mata -> eventSourcing((Class<?>) mata.getTarget(), mata.getId())).collect(Collectors.toList());
    }
}
